/*-------------------------------------------------------------------------
 *
 * recovery_gen.c
 *		恢复配置生成器
 *
 * Portions Copyright (c) 2011-2022, PostgreSQL Global Development Group
 *
 *-------------------------------------------------------------------------
 */
#include "postgres_fe.h"

#include "common/logging.h"
#include "fe_utils/recovery_gen.h"
#include "fe_utils/string_utils.h"

static char *fc_escape_quotes(const char *fc_src);

/*
 * 将恢复配置内容写入新的 PQExpBuffer，并返回它。
 */
PQExpBuffer
GenerateRecoveryConfig(PGconn *fc_pgconn, char *fc_replication_slot)
{
	PQconninfoOption *fc_connOptions;
	PQExpBufferData fc_conninfo_buf;
	char	   *fc_escaped;
	PQExpBuffer fc_contents;

	Assert(fc_pgconn != NULL);

	fc_contents = createPQExpBuffer();
	if (!fc_contents)
		pg_fatal("out of memory");

	/*
	 * 在 PostgreSQL 12 及更高版本中，standby_mode 已被删除，取而代之的是
	 * standby.signal 用以在恢复期间触发备用状态。
	 */
	if (PQserverVersion(fc_pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC)
		appendPQExpBufferStr(fc_contents, "standby_mode = 'on'\n");

	fc_connOptions = PQconninfo(fc_pgconn);
	if (fc_connOptions == NULL)
		pg_fatal("out of memory");

	initPQExpBuffer(&fc_conninfo_buf);
	for (PQconninfoOption *fc_opt = fc_connOptions; fc_opt && fc_opt->keyword; fc_opt++)
	{
		/* 忽略空设置和那些 libpqwalreceiver 的覆盖设置。 */
		if (strcmp(fc_opt->keyword, "replication") == 0 ||
			strcmp(fc_opt->keyword, "dbname") == 0 ||
			strcmp(fc_opt->keyword, "fallback_application_name") == 0 ||
			(fc_opt->val == NULL) ||
			(fc_opt->val != NULL && fc_opt->val[0] == '\0'))
			continue;

		/* 用空格分隔键值对 */
		if (fc_conninfo_buf.len != 0)
			appendPQExpBufferChar(&fc_conninfo_buf, ' ');

		/*
		 * 写入 "keyword=value" 片段，值字符串会在必要时进行转义和/或
		 * 加引号。
		 */
		appendPQExpBuffer(&fc_conninfo_buf, "%s=", fc_opt->keyword);
		appendConnStrVal(&fc_conninfo_buf, fc_opt->val);
	}
	if (PQExpBufferDataBroken(fc_conninfo_buf))
		pg_fatal("out of memory");

	/*
	 * 转义连接字符串，以便它可以放入配置文件中。
	 * 注意这与上面单个连接选项的转义是不同的！
	 */
	fc_escaped = fc_escape_quotes(fc_conninfo_buf.data);
	termPQExpBuffer(&fc_conninfo_buf);
	appendPQExpBuffer(fc_contents, "primary_conninfo = '%s'\n", fc_escaped);
	free(fc_escaped);

	if (fc_replication_slot)
	{
		/* 未转义：ReplicationSlotValidateName 允许 [a-z0-9_] 仅 */
		appendPQExpBuffer(fc_contents, "primary_slot_name = '%s'\n",
						  fc_replication_slot);
	}

	if (PQExpBufferBroken(fc_contents))
		pg_fatal("out of memory");

	PQconninfoFree(fc_connOptions);

	return fc_contents;
}

/*
 * 在 target_dir 指定的目录中写入配置文件，
 * 将已收集到内存中的内容附加在其中。 然后将信号文件写入 target_dir。
 * 如果服务器不支持作为 GUC 的恢复参数，则不需要信号文件，
 * 配置将被写入 recovery.conf。
 */
void WriteRecoveryConfig(PGconn *fc_pgconn, char *fc_target_dir, PQExpBuffer fc_contents)
{
	char		fc_filename[MAXPGPATH];
	FILE	   *fc_cf;
	bool		fc_use_recovery_conf;

	Assert(fc_pgconn != NULL);

	fc_use_recovery_conf =
		PQserverVersion(fc_pgconn) < MINIMUM_VERSION_FOR_RECOVERY_GUC;

	snprintf(fc_filename, MAXPGPATH, "%s/%s", fc_target_dir,
			 fc_use_recovery_conf ? "recovery.conf" : "postgresql.auto.conf");

	fc_cf = fopen(fc_filename, fc_use_recovery_conf ? "w" : "a");
	if (fc_cf == NULL)
		pg_fatal("could not open file \"%s\": %m", fc_filename);

	if (fwrite(fc_contents->data, fc_contents->len, 1, fc_cf) != 1)
		pg_fatal("could not write to file \"%s\": %m", fc_filename);

	fclose(fc_cf);

	if (!fc_use_recovery_conf)
	{
		snprintf(fc_filename, MAXPGPATH, "%s/%s", fc_target_dir, "standby.signal");
		fc_cf = fopen(fc_filename, "w");
		if (fc_cf == NULL)
			pg_fatal("could not create file \"%s\": %m", fc_filename);

		fclose(fc_cf);
	}
}

/*
 * 转义字符串，以便它可以作为配置文件中键值对的值使用。
 */
static char * fc_escape_quotes(const char *fc_src)
{
	char	   *fc_result = escape_single_quotes_ascii(fc_src);

	if (!fc_result)
		pg_fatal("out of memory");
	return fc_result;
}
